package io.cici.cc.mybatis.plus.core;

import io.cici.cc.mybatis.lite.builder.Builder;
import io.cici.cc.mybatis.lite.builder.BuilderException;
import io.cici.cc.mybatis.lite.mapping.SqlSource;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.sql_source.DynamicSqlSource;
import io.cici.cc.mybatis.lite.sql_source.RawSqlSource;
import io.cici.cc.mybatis.lite.sql_source.scripting.tag.MixedTag;
import io.cici.cc.mybatis.lite.sql_source.scripting.tag.StaticTextTag;
import io.cici.cc.mybatis.lite.sql_source.scripting.tag.Tag;
import io.cici.cc.mybatis.lite.sql_source.scripting.tag.TextTag;
import io.cici.cc.mybatis.plus.core.tagHandler.*;
import io.cici.cc.mybatis.plus.core.toolkit.StringUtils;
import org.w3c.dom.NodeList;

import java.lang.ref.WeakReference;
import java.util.*;

/**
 * <p>
 * 试验性功能,解决mybatis堆内存过大的问题(看后期mybatis官方会不会解决堆内存占用问题)
 * </p>
 * <p>
 * 由于大量重复sql节点,导致堆内存过大(本质上属于string导致的堆内存增大问题)
 * </p>
 * <p>
 * 例如: {@code <if test="createTime!=null">create_time=#{createTime}</if>}等公共字段
 * </p>
 * <p>
 * 解决方案: 将生成的xml节点值写入字符串常量池,减少后面重复字符串导致的问题
 * <li>方案一:
 * 缓存一些特定的mybatis-plus生成的占位符与表达式和项目公共字段(改动有点多,需要增加一些特定xml属性来标记是mybatis-plus生成的节点,减少堆内存较少)
 * </li>
 * <li>方案二: 直接将节点内容intern写入至字符串常量池(改动少,减少堆内存多,弊端可能会将一些无重复的字符串写入至常量池)</li>
 * <li>方案三: 模拟字符串常量池,减少重复字符串写入至堆内存(代码相对来说不好看点)</li>
 * </p>
 */
public class MybatisXMLScriptBuilder extends Builder {


    /**
     * 也可以将XNode节点包裹增强一下,来减少方法的引用,但需要创建对象,这里就直接将每个地方手动改一下了.
     */
    private static final Map<String, WeakReference<String>> CACHE_STRING = new WeakHashMap<>();
    public final Map<String, TagHandler> nodeHandlerMap = new HashMap<>();
    private final io.cici.cc.mybatis.lite.parse.Node node;
    private final Class<?> parameterType;
    private boolean isDynamic;

    public MybatisXMLScriptBuilder(Configuration configuration, io.cici.cc.mybatis.lite.parse.Node node, Class<?> parameterType) {
        super(configuration);
        this.node = node;
        this.parameterType = parameterType;
        initNodeHandlerMap();
    }

    public synchronized static String cacheStr(String string) {
        if (string == null) {
            return null;
        }
        String value = CACHE_STRING.computeIfAbsent(string, WeakReference::new).get();
        // 增强安全处理一下,如果实在是GC处理掉了(可能性小),就返回原来.
        if (StringUtils.isNotBlank(value)) {
            return value;
        }
        return string;
    }

    private void initNodeHandlerMap() {
        nodeHandlerMap.put("if", new IfHandler(this));
        nodeHandlerMap.put("choose", new ChooseHandler(this));
        nodeHandlerMap.put("when", new IfHandler(this));
        nodeHandlerMap.put("otherwise", new OtherwiseHandler(this));
        nodeHandlerMap.put("foreach", new ForEachHandler(this));
        nodeHandlerMap.put("bind", new BindHandler());
    }

    public SqlSource parseScriptNode() {
        MixedTag mixedTag = parseDynamicTags(node);
        SqlSource sqlSource;
        if (isDynamic) {
            sqlSource = new DynamicSqlSource(configuration, mixedTag);
        } else {
            sqlSource = new RawSqlSource(configuration, mixedTag, parameterType);
        }
        return sqlSource;
    }

    public MixedTag parseDynamicTags(io.cici.cc.mybatis.lite.parse.Node node) {
        List<Tag> tagList = new ArrayList<>();
        NodeList childNodes = node.getNode().getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            io.cici.cc.mybatis.lite.parse.Node childNode = node.newNode(childNodes.item(i));
            if (childNode.getNode().getNodeType() == org.w3c.dom.Node.CDATA_SECTION_NODE
                    || childNode.getNode().getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
                String text = cacheStr(childNode.getBody(""));
                TextTag textTag = new TextTag(text);
                if (textTag.hasVariable()) {
                    tagList.add(textTag);
                    isDynamic = true;
                } else {
                    tagList.add(new StaticTextTag(text));
                }
            } else if (childNode.getNode().getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { // issue #628
                String nodeName = childNode.getNode().getNodeName();
                TagHandler tagHandler = nodeHandlerMap.get(nodeName);
                if (tagHandler == null) {
                    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                }
                tagHandler.handleNode(childNode, tagList);
                isDynamic = true;
            }
        }
        return new MixedTag(tagList);
    }


}
